package com.sunlee.admin.api.module.job.task;

import com.sunlee.admin.api.module.job.dto.CrawlerFirstPptFontTaskDTO;
import com.sunlee.admin.api.util.FileUtil;
import com.sunlee.admin.api.util.JsoupUtil;
import com.sunlee.common.core.enums.ErrorCodeEnum;
import com.sunlee.common.core.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 第一PPT网，字体资源爬取
 *
 * @author sunulee
 */
@Slf4j
@Component("crawlerFirstPptFontTask")
public class CrawlerFirstPptFontTask implements ITask {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 资源详情页地址
     */
    private final static String FONT_FORMAT_URL = "https://www.1ppt.com/ziti/ziti_{0}.html";
    private final static String FONT_DETAIL_URL = "https://www.1ppt.com/ziti/{0}.html";

    /**
     * 资源下载页地址
     */
    private final static String DOWNLOAD_URL_FORMAT = "https://www.1ppt.com/plus/download.php?aid={0}";

    private final static String PUBLIC_KEY = "3000176000856006061501533003690027800375";

    private final static HashMap<String, String> COOKIE = new HashMap<>(4);

    private final static Pattern PATTERN_ARG1 = Pattern.compile("var arg1='([^']*)';");

    private final static Map<Object, Object> HEADERS = new HashMap<>(4);

    /**
     * 防止重复执行
     */
    private static boolean isRun = false;

    @Override
    public void run(String params) {
        log.info("crawlerFirstPptFontTask定时任务正在执行，参数为：{}", params);

        if (isRun) {
            throw new BusinessException("不可重复执行");
        }
        isRun = true;

        try {
            start(validaDTO(params));
        } catch (Exception e) {
            log.error("任务结束，字体下载异常", e);
        } finally {
            isRun = false;
        }
    }

    private CrawlerFirstPptFontTaskDTO validaDTO(String params) {
        CrawlerFirstPptFontTaskDTO dto = validaParseParam(params, CrawlerFirstPptFontTaskDTO.class);

        // 校验参数
        if (Objects.isNull(dto) || Objects.isNull(dto.getTargetFolder())) {
            throw new BusinessException(ErrorCodeEnum.PARAM_NOT_COMPLETE);
        }

        // 起始和结束页
        int startPage = Objects.nonNull(dto.getStartPage()) ? dto.getStartPage() : 1;
        int endPage = Objects.nonNull(dto.getEndPage()) ? dto.getEndPage() : 100;

        dto.setStartPage(startPage);
        dto.setEndPage(endPage);

        return dto;
    }

    public void start(CrawlerFirstPptFontTaskDTO dto) {

        // 创建压缩包存储目录
        String fontParentPath = dto.getTargetFolder() + File.separator;
        if (!FileUtil.isDirectory(fontParentPath)) {
            if (!FileUtil.makeDirectory(fontParentPath)) {
                throw new BusinessException("创建目录失败：" + fontParentPath);
            }
        }

        // 遍历页数爬取
        for (int num = dto.getStartPage(); num <= dto.getEndPage(); num++) {
            // 拼接地址
            String url = MessageFormat.format(FONT_FORMAT_URL, num);
            // 获取资源id
            List<String> fontIds = getFontIdsByUtl(url);
            // 根据id下载zip到指定目录
            List<String> fontZipPathList = downloadFontZipByIds(fontIds, fontParentPath);
            log.info("下载字体：页数{},总数{}", num, fontZipPathList.size());
        }
    }

    /**
     * 爬取资源列表，获取所有资源ID
     *
     * @param url 请求的资源列表页url
     * @return 资源ID集合
     */
    private List<String> getFontIdsByUtl(String url) {
        Document document;
        try {
            document = JsoupUtil.connectByGet(url);
        } catch (IOException e) {
            log.error("爬取列表页异常：{}", url, e);
            return null;
        }
        Elements tpList = document.getElementsByClass("tplist");

        List<String> resUrls = tpList.select("li").select("h2").select("a").eachAttr("href");

        if (CollectionUtils.isEmpty(resUrls)) {
            return null;
        }

        // 只取第三方资源id
        return resUrls.stream().map(item ->
                        item.replace("/ziti/", "")
                                .replace(".html", ""))
                .collect(Collectors.toList());
    }


    /**
     * 根据id下载到指定目录下
     *
     * @param fontIds       资源id
     * @param zipParentPath 指定目录
     * @return 存储路径
     */
    private List<String> downloadFontZipByIds(List<String> fontIds, String zipParentPath) {
        try {
            List<String> zipPathList = new ArrayList<>();


            fontIds.forEach(id -> {
                // 封装下载页地址
                String downloadPageUrl = MessageFormat.format(DOWNLOAD_URL_FORMAT, id);

                Document downloadDocument;
                try {
                    // 第一次空头请求
                    String firstGetHtml = JsoupUtil.connectByGet(downloadPageUrl).html();
                    String arg1 = regGetArg1(firstGetHtml);
                    if (StringUtils.isBlank(arg1)) {
                        throw new BusinessException("反爬虫规则变更！！！");
                    }

                    // 处理二次请求前加密
                    String boxStr = unsBox(arg1);
                    String acwScV2 = hexXor(boxStr, PUBLIC_KEY);
                    COOKIE.put("acw_sc__v2", acwScV2);

                    // 防止重定向
                    HEADERS.put("Referer", MessageFormat.format(FONT_DETAIL_URL, id));
                    downloadDocument = JsoupUtil.getDocument(downloadPageUrl, 0, COOKIE, HEADERS).get();
                } catch (IOException e) {
                    log.error("crawlerResourceTask-request-download-io-error：{}", downloadPageUrl, e);
                    throw new BusinessException("网络请求异常");
                }


                // 解析下载页地址
                Elements aElements = downloadDocument.getElementsByClass("downloadlist").select("a");
                String zipUrl = aElements.get(0).attr("href");

                if (!zipUrl.contains("soft")) {
                    zipUrl = aElements.get(1).attr("href");
                }

                zipUrl = zipUrl.replaceAll("[-]{2,}", "")
                        .replace("-1.", ".")
                        .replace("====", "");

                if (!zipUrl.endsWith(".rar") && !zipUrl.endsWith(".zip")) {
                    log.info("不存在字体压缩包={}", zipUrl);
                    return;
                }

                log.info("下载字体，id={},url={}", id, zipUrl);
                String fontZipPath = zipParentPath + FileUtil.getNamePart(zipUrl);
                // 下载页面的zip包
                downloadFont(fontZipPath, zipUrl, 3);

                // 记录下载后地址
                zipPathList.add(fontZipPath);
            });
            return zipPathList;
        } catch (Exception e) {
            throw new BusinessException("下载字体时出错", e);
        }
    }

    private void downloadFont(String zipParentPath, String zipUrl, int count) {
        if (count <= 0) {
            log.error("多次下载失败：{}", zipUrl);
            return;
        }

        RequestCallback requestCallback = request -> request.getHeaders()
                .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
        // 下载文件，对响应进行流式处理而不是将其全部加载到内存中
        restTemplate.execute(zipUrl, HttpMethod.GET, requestCallback, clientHttpResponse -> {
            // 先删除旧文件再下载
            if (cn.hutool.core.io.FileUtil.exist(zipParentPath)) {
                cn.hutool.core.io.FileUtil.del(zipParentPath);
            }
            Files.copy(clientHttpResponse.getBody(), Paths.get(zipParentPath));
            return null;
        });

        File file = new File(zipParentPath);
        if (!file.exists() || file.length() == 0) {
            downloadFont(zipParentPath, zipUrl, --count);
        }
    }


    public static String regGetArg1(String html) {
        Matcher m3 = PATTERN_ARG1.matcher(html);
        if (m3.find()) {
            return m3.group(1);
        }
        return null;
    }

    private static String unsBox(String arg1) {
        int[] _0x4b082b = new int[]{0xf, 0x23, 0x1d, 0x18, 0x21, 0x10, 0x1, 0x26, 0xa, 0x9, 0x13, 0x1f, 0x28, 0x1b, 0x16, 0x17, 0x19, 0xd, 0x6, 0xb, 0x27, 0x12, 0x14, 0x8, 0xe, 0x15, 0x20, 0x1a, 0x2, 0x1e, 0x7, 0x4, 0x11, 0x5, 0x3, 0x1c, 0x22, 0x25, 0xc, 0x24};
        char[] _0x4da0dc = new char[arg1.length()];
        String _0x12605e = "";
        for (int _0x20a7bf = 0; _0x20a7bf < arg1.length(); _0x20a7bf++) {
            char _0x385ee3 = arg1.charAt(_0x20a7bf);
            for (int _0x217721 = 0; _0x217721 < _0x4b082b.length; _0x217721++) {
                if (_0x4b082b[_0x217721] == _0x20a7bf + 1) {
                    _0x4da0dc[_0x217721] = _0x385ee3;
                }
            }
        }
        for (char c : _0x4da0dc) {
            _0x12605e += String.valueOf(c);
        }
        return _0x12605e;
    }

    private static String hexXor(String s1, String _0x4e08d8) {
        String _0x5a5d3b = "";
        for (int _0xe89588 = 0; _0xe89588 < s1.length() && _0xe89588 < _0x4e08d8.length(); _0xe89588 += 2) {
            int _0x401af1 = Integer.parseInt(s1.substring(_0xe89588, _0xe89588 + 2), 16);
            int _0x105f59 = Integer.parseInt(_0x4e08d8.substring(_0xe89588, _0xe89588 + 2), 16);
            int _0x189e2c_10 = _0x401af1 ^ _0x105f59;
            String _0x189e2c = Integer.toHexString(_0x189e2c_10);
            if (_0x189e2c.length() == 1) {
                _0x189e2c = '0' + _0x189e2c;
            }
            _0x5a5d3b += _0x189e2c;
        }
        return _0x5a5d3b;
    }
}
